/* Copyright (c) 2004-2024 Peigen.info. All rights reserved. */

package info.peigen.hotpot.component.gateway.dianlan.annotations;

import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.*;
import com.alibaba.fastjson2.JSON;
import info.peigen.hotpot.common.core.result.ResultCode;
import info.peigen.hotpot.common.service.base.exception.BusinessException;
import info.peigen.hotpot.component.gateway.core.util.Cp56Time2aUtil;
import info.peigen.hotpot.component.gateway.core.util.HexUtil;
import info.peigen.hotpot.component.gateway.dianlan.protocol.passive.DianlanBmsAndLiquidCoolingUnit;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * <b>(DianlanProtocolParse)</b>
 *
 * @author Peigen
 * @version 1.0.0
 * @since 2022/8/18
 */
@Slf4j
public class DianlanProtocolParse {
    public final static  String DIANLAN_START_SIGN = "01101000";
    private final static String BIT_CLASS_PACKAGE  = "info.peigen.hotpot.component.gateway.dianlan.bits.";
    private final static String ENUM_CLASS_PACKAGE = "info.peigen.hotpot.component.gateway.dianlan.enum.";

    /**
     * 解析成对象
     *
     * @param <T>    对象类型
     * @param bitStr 二进制字符串
     * @param cls    类名
     *
     * @return 对象
     *
     * @throws UtilException 包装各类异常
     */
    @SuppressWarnings("unchecked")
    public static <T> T deserialization(String bitStr, Class cls) {
        T t;
        try {
            t = (T) cls.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                 NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        Field[]     fields    = ReflectUtil.getFieldsDirectly(cls, true);
        List<Field> fieldList = Arrays.stream(fields).distinct().collect(Collectors.toList());
        // 坐标
        AtomicInteger location = new AtomicInteger();
        String[]      bitArray = StrUtil.split(bitStr, 8);
        fieldList.forEach(field -> {
            DianlanProtocol protocol = field.getAnnotation(DianlanProtocol.class);

            if (ObjectUtil.isNull(protocol)) {
                return;
            }

            String bit = ArrayUtil.join(ArrayUtil.sub(bitArray, location.get(), location.get() + protocol.length()), "");
            location.addAndGet(protocol.length());
            String result = HexUtil.binaryToHex(bit);

            switch (protocol.dataType()) {
                case ASCII:
                    result = deserializationAscii(result);
                    ReflectUtil.setFieldValue(t, field, result);
                    break;
                case INT:
                    result = String.valueOf(Integer.parseInt(result, 16));
                    ReflectUtil.setFieldValue(t, field, result);
                    break;
                case DATA:
                    bit = ArrayUtil.join(ArrayUtil.sub(bitArray, location.get(), protocol.end()), "");
                    ReflectUtil.setFieldValue(t, field, bit);
                    break;
                case END:
                    bit = ArrayUtil.join(ArrayUtil.sub(bitArray, bitArray.length - 2, bitArray.length), "");
                    result = HexUtil.binaryToHex(bit).toUpperCase();
                    ReflectUtil.setFieldValue(t, field, result);
                    break;
                case ENUM:
                    deserializationEnum(t, field, result);
                    break;
                case BIT:
                    deserializationBits(t, field, result, bit);
                    break;
                case TIME:
                    result = Cp56Time2aUtil.decode(result);
                    ReflectUtil.setFieldValue(t, field, result);
                    break;
                case OBJ:
                    deserializationObj(t, field, result);
                    break;
                case BCD:
                default:
                    ReflectUtil.setFieldValue(t, field, result);
            }
        });
        return t;
    }

    public static void main(String[] args) {
        DianlanBmsAndLiquidCoolingUnit result = deserialization("0011001000000001000001100000000000010100010000010111000000000000000000100000000100000001000000000000000110100110011001000110010000100101000110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000000000000000000010000000100000000000000000000000100111101010100100000000001010010000000000101001000010000", DianlanBmsAndLiquidCoolingUnit.class);
        System.out.println(JSON.toJSONString(result));
    }

    /**
     * 对象转字节数组
     *
     * @param obj 对象
     *
     * @return byte[]
     */
    public static byte[] serialization(Object obj) {
        Class       cls       = obj.getClass();
        Field[]     fields    = ArrayUtil.addAll(cls.getFields(), cls.getDeclaredFields());
        List<Field> fieldList = Arrays.stream(fields).distinct().collect(Collectors.toList());
        String result = fieldList.stream().map(field -> {
            DianlanProtocol protocol = field.getAnnotation(DianlanProtocol.class);

            switch (protocol.dataType()) {
                case ENUM:
                    return serializationEnum(obj, field);
                case BCD:
                default:
                    return HexUtil.hexToBinary((String) ReflectUtil.getFieldValue(obj, field));
            }
        }).collect(Collectors.joining());
        return result.getBytes();
    }

    public static String getData(Object obj) {
        StringBuilder sb        = new StringBuilder();
        Class         cls       = obj.getClass();
        Field[]       fields    = ArrayUtil.addAll(cls.getFields(), cls.getDeclaredFields());
        List<Field>   fieldList = Arrays.stream(fields).distinct().collect(Collectors.toList());
        for (Field field : fieldList) {
            DianlanProtocol protocol = field.getAnnotation(DianlanProtocol.class);
            switch (protocol.dataType()) {
                case ENUM:
                    sb.append(serializationEnumString(obj, field));
                    break;
                default:
                    sb.append(ReflectUtil.getFieldValue(obj, field));
            }
        }
        return sb.toString();
    }


    public static boolean dianlanProtocol(String bitStr) {
        return StrUtil.equals(StrUtil.sub(bitStr, 0, 8), DianlanProtocolParse.DIANLAN_START_SIGN);
    }

    private static String deserializationAscii(String result) {
        return StrUtil.trim(cn.hutool.core.util.HexUtil.decodeHexStr(cn.hutool.core.util.HexUtil.format(result)));
    }

    private static void deserializationEnum(Object t, Field field, String result) {
        try {
            Method getCode = field.getType().getMethod("getCode");
            Object obj = Arrays.stream(field.getType().getEnumConstants()).filter(code ->
                            StrUtil.equals(ReflectUtil.invoke(code, getCode), result))
                    .findFirst().get();
            ReflectUtil.setFieldValue(t, field, obj);
        } catch (Exception e) {
            log.info("有位置枚举类型：" + result + ",t:" + t);
        }
    }

    private static void deserializationObj(Object t, Field field, String hexStr) {
        DianlanProtocol protocol    = field.getAnnotation(DianlanProtocol.class);
        String          objClassStr = protocol.dataTypeClass();
        Object          obj         = ClassUtil.invoke(BIT_CLASS_PACKAGE + objClassStr + "#parseReason", new Object[]{hexStr});
        ReflectUtil.setFieldValue(t, field, obj);
    }

    private static String serializationEnumString(Object obj, Field field) {
        Assert.isTrue(field.getType().isEnum(), () -> new BusinessException(ResultCode.FAILURE, "属性必须为enum,field=" + field.getName()));

        Object enumObj = Arrays.stream(field.getType().getEnumConstants())
                .filter(code -> code == ReflectUtil.getFieldValue(obj, field))
                .findFirst().get();
        try {
            Method method  = field.getType().getMethod("code");
            String enumStr = (String) method.invoke(enumObj);
            return enumStr;
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private static String serializationEnum(Object obj, Field field) {
        return HexUtil.hexToBinary(serializationEnumString(obj, field));
    }

    private static void deserializationBits(Object t, Field field, String result, String hex) {
        DianlanProtocol protocol    = field.getAnnotation(DianlanProtocol.class);
        String          bitClassStr = protocol.dataTypeClass();
        Method          method      = ReflectUtil.getMethodByName(ClassUtil.loadClass(BIT_CLASS_PACKAGE + bitClassStr), "parseReason");
        if (ObjectUtil.isNotNull(method)) {
            Object obj = ClassUtil.invoke(BIT_CLASS_PACKAGE + bitClassStr + "#parseReason", new Object[]{Integer.parseInt(result)});
            ReflectUtil.setFieldValue(t, field, obj);
        } else {
            Object obj = ClassUtil.invoke(BIT_CLASS_PACKAGE + bitClassStr + "#parseBits", new Object[]{hex});
            ReflectUtil.setFieldValue(t, field, obj);
        }
    }

}